//
//  LcSecurityCenter.m
//  18Birdies
//
//  Created by Lc on 15/3/20.
//  Copyright (c) 2015年 Lc. All rights reserved.
//

#import <CommonCrypto/CommonCrypto.h>

#import "LcSecurityCenter.h"
#import "LcSecurityException.h"
#import "NSString+Lc.h"

@implementation LcSecurityCenter

- (NSString*)base64Encode:(NSData*)data {
  return [data base64EncodedStringWithOptions:kNilOptions];
}

- (NSData*)base64Decode:(NSString*)string {
  return
      [[NSData alloc] initWithBase64EncodedString:string options:kNilOptions];
}

- (NSString*)md5:(NSData*)data {
  if (data.length == 0) {
    return nil;
  }
  unsigned char md5[CC_MD5_DIGEST_LENGTH];
  CC_MD5(data.bytes, (CC_LONG)data.length, md5);
  NSMutableString* result =
      [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
  for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
    [result appendFormat:@"%02x", md5[i]];
  }
  return result;
}

- (NSString*)sha1:(NSData*)data {
  if (data.length == 0) {
    return nil;
  }
  unsigned char sha1[CC_SHA1_DIGEST_LENGTH];
  CC_SHA1(data.bytes, (CC_LONG)data.length, sha1);
  NSMutableString* result =
      [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
  for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; ++i) {
    [result appendFormat:@"%02x", sha1[i]];
  }
  return result;
}

- (NSData*)randomAes128Key {
  int keyLength = 16;
  char* keyArray = malloc(keyLength * sizeof(char));
  for (int i = 0; i < keyLength; ++i) {
    keyArray[i] = (char)arc4random_uniform(256);
  }
  return [NSData dataWithBytesNoCopy:keyArray length:keyLength * sizeof(char)];
}

- (NSData*)encrypt:(NSData*)data
     withAes128Key:(NSData*)aesKey
                iv:(NSString*)iv {
  if (data.length == 0) {
    return nil;
  }

  NSUInteger dataLength = data.length;

  size_t outputBufferLength = dataLength + kCCBlockSizeAES128;
  void* outputBuffer = malloc(outputBufferLength);
  memset(outputBuffer, 0, outputBufferLength);

  size_t actualOutputLength = 0;
  CCCryptorStatus cryptorStatus =
      CCCrypt(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, aesKey.bytes,
              kCCKeySizeAES128, iv.UTF8String, data.bytes, dataLength,
              outputBuffer, outputBufferLength, &actualOutputLength);

  if (cryptorStatus == kCCSuccess) {
    NSData* outputData =
        [NSData dataWithBytesNoCopy:outputBuffer length:actualOutputLength];
    return outputData;
  } else {
    free(outputBuffer);
    @throw [LcSecurityException securityExceptionWithReason:@"aes encrypt error"
                                               andErrorCode:cryptorStatus];
  }
}

- (NSData*)decrypt:(NSData*)data
     withAes128Key:(NSData*)aesKey
                iv:(NSString*)iv {
  if (data.length == 0) {
    return nil;
  }

  NSUInteger dataLength = data.length;

  size_t outputBufferLength = dataLength + kCCBlockSizeAES128;
  void* outputBuffer = malloc(outputBufferLength);
  memset(outputBuffer, 0, outputBufferLength);

  size_t actualOutputLength = 0;
  CCCryptorStatus cryptorStatus =
      CCCrypt(kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, aesKey.bytes,
              kCCKeySizeAES128, iv.UTF8String, data.bytes, dataLength,
              outputBuffer, outputBufferLength, &actualOutputLength);

  if (cryptorStatus == kCCSuccess) {
    NSData* outputData =
        [NSData dataWithBytesNoCopy:outputBuffer length:actualOutputLength];
    return outputData;
  } else {
    free(outputBuffer);
    @throw [LcSecurityException securityExceptionWithReason:@"aes decrypt error"
                                               andErrorCode:cryptorStatus];
  }
}

- (NSData*)encryptString:(NSString*)string
           withAes128Key:(NSData*)aesKey
                      iv:(NSString*)iv {
  return [self encrypt:string.toData withAes128Key:aesKey iv:iv];
}

- (NSString*)decryptData:(NSData*)data
           withAes128Key:(NSData*)aesKey
                      iv:(NSString*)iv {
  NSData* decrypted = [self decrypt:data withAes128Key:aesKey iv:iv];
  if (decrypted == nil) {
    return nil;
  }
  return [NSString stringWithData:decrypted];
}

- (NSData*)encrypt:(NSData*)data withRsaPubicKey:(NSString*)rsaPublicKeyString {
  if (data.length == 0) {
    return nil;
  }

  SecKeyRef rsaPublicKey = [self rsaPublicKeyFromString:rsaPublicKeyString];
  size_t cipherLength = SecKeyGetBlockSize(rsaPublicKey);
  //  LcLog(@"cipherLength=%zu", cipherLength);
  // PKCS1 padding need 11 bytes padding area
  size_t maxDataLength = cipherLength - 11;
  size_t dataLength = data.length;
  if (dataLength > maxDataLength) {
    CFRelease(rsaPublicKey);
    @throw [LcSecurityException
        securityExceptionWithReason:
            [NSString
                stringWithFormat:@"data too long, cannot exceed %zu bytes",
                                 maxDataLength]
                       andErrorCode:0];
  }

  void* outputBuffer = malloc(cipherLength);
  size_t outputLength = cipherLength;
  OSStatus status = SecKeyEncrypt(rsaPublicKey, kSecPaddingPKCS1, data.bytes,
                                  dataLength, outputBuffer, &outputLength);
  CFRelease(rsaPublicKey);
  if (status != noErr) {
    free(outputBuffer);
    @throw
        [LcSecurityException securityExceptionWithReason:@"SecKeyEncrypt failed"
                                            andErrorCode:status];
  }

  NSData* encrypted = [NSData dataWithBytes:outputBuffer length:outputLength];
  free(outputBuffer);
  return encrypted;
}

- (NSData*)encryptString:(NSString*)string
         withRsaPubicKey:(NSString*)rsaPublicKeyString {
  return [self encrypt:string.toData withRsaPubicKey:rsaPublicKeyString];
}

- (SecKeyRef)rsaPublicKeyFromString:(NSString*)rsaPublicKeyString {
  SecCertificateRef certificate = NULL;
  SecPolicyRef policy = NULL;
  SecTrustRef trust = NULL;
  @try {
    certificate = SecCertificateCreateWithData(
        kCFAllocatorDefault,
        (__bridge CFDataRef)
            [[NSData alloc] initWithBase64EncodedString:rsaPublicKeyString
                                                options:kNilOptions]);
    if (certificate == NULL) {
      @throw [LcSecurityException
          securityExceptionWithReason:@"invalid rsaPublicKeyString"
                         andErrorCode:0];
    }
    policy = SecPolicyCreateBasicX509();
    OSStatus status =
        SecTrustCreateWithCertificates(certificate, policy, &trust);
    if (status != noErr) {
      @throw [LcSecurityException
          securityExceptionWithReason:@"SecTrustCreateWithCertificates failed"
                         andErrorCode:status];
    }

    SecTrustResultType trustResultType;
    status = SecTrustEvaluate(trust, &trustResultType);
    if (status != noErr) {
      @throw [LcSecurityException
          securityExceptionWithReason:@"SecTrustEvaluate failed"
                         andErrorCode:status];
    }

    SecKeyRef publicKey = SecTrustCopyPublicKey(trust);
    if (publicKey == NULL) {
      @throw [LcSecurityException
          securityExceptionWithReason:@"SecTrustCopyPublicKey failed"
                         andErrorCode:0];
    }

    return publicKey;
  } @finally {
    // LcLog(@"finally in rsaPublicKeyFromString:");
    if (trust != NULL) {
      CFRelease(trust);
    }
    if (policy != NULL) {
      CFRelease(policy);
    }
    if (certificate != NULL) {
      CFRelease(certificate);
    }
  }
}

- (NSString*)generateUUID {
  NSUUID* uuid = [NSUUID UUID];
  return uuid.UUIDString.lowercaseString;
}

@end
